Skip to content

fix(zod): parser generation when content-type contains charset precision - #3404

Closed
pierre-isabel-bbc wants to merge 4 commits into
orval-labs:masterfrom
pierre-isabel-bbc:fix/zod-parsers-content-type-w-charset
Closed

fix(zod): parser generation when content-type contains charset precision#3404
pierre-isabel-bbc wants to merge 4 commits into
orval-labs:masterfrom
pierre-isabel-bbc:fix/zod-parsers-content-type-w-charset

Conversation

@pierre-isabel-bbc

@pierre-isabel-bbc pierre-isabel-bbc commented May 20, 2026

Copy link
Copy Markdown

Reproduction

OpenAPI contract Content-Type may contain charset reference, like in the example below:

paths:
  /test-endpoint:
    get:
      operationId: getTestEndpoint
      description: This is a test endpoint
      responses:
        "200":
          description: return object.
          content:
            "application/json":
              schema:
                $ref: "#/components/schemas/ResponseObject"
        "400":
          description: Bad request.
          content:
            "application/json; charset=UTF-8":
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Error message
              example:
                value:
                  $ref: examples/error.json

The issue was that parser for 200 was generated, but not for 400 because of the charset=utf-8 precision.

What this PR does

  • Ignores charset in this case as this does not provide useful information for zod generation
  • Does not take into account this possibility on other packages

Summary by CodeRabbit

  • Bug Fixes

    • Improved content-type detection to recognize media types with parameters (e.g., charset) and more reliably choose request/response content types; improved response selection priority (favoring 200 over other 2xx variants) for JSON and multipart/form-data scenarios.
  • Tests

    • Added tests covering multipart/form-data requests with field-level encoding and charset-qualified JSON response content types to validate correct schema generation.

Review Change Stack

Copilot AI review requested due to automatic review settings May 20, 2026 10:58
@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Normalize content-type keys by trimming parameters before matching JSON or multipart/form-data in parseBodyAndResponse; preserve schema dereference, array handling, and Zod generation paths. Add getSingleResponse to pick one response (200 → 2XX → 2xx). Add charset-qualified tests.

Changes

Charset-normalized media-type handling

Layer / File(s) Summary
Media-type detection, schema selection, and lint scope
packages/zod/src/index.ts
parseBodyAndResponse now iterates resolvedRef.content and uses isMediaType to match the normalized media-type (portion before ;, trimmed/lowercased) for application/json and multipart/form-data; downstream dereference, array handling, and choice between generateFormDataZodSchema and generateZodValidationSchemaDefinition are preserved. getSingleResponse added to select one response using priority 2002XX2xx. The earlier /* eslint-disable ... */ block is closed with /* eslint-enable ... */ before parseParameters.
Charset-qualified media-type tests
packages/zod/src/zod.test.ts
New test constructs a multipart/form-data; charset=utf-8 requestBody and application/json; charset=utf-8 response, runs generateZod with response generation enabled, and asserts generated UploadFormBody and UploadFormResponse schemas match expectations for file vs string mappings and response fields.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • orval-labs/orval#3293: Addresses multipart media-type selection and form data schema generation via writeZodSchemasFromVerbs and generateFormDataZodSchema.

Suggested labels

zod

Suggested reviewers

  • melloware

Poem

🐰 I sniffed the charset floating on the breeze,
Trimmed after the semicolon with nimble ease.
JSON and forms now find their rightful name,
Matching schemas clean — the rabbit claims the fame.
🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: handling content-type headers with charset parameters in Zod parser generation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds support for OpenAPI media types that include parameters (e.g., ; charset=utf-8) when generating Zod schemas, and introduces a regression test to ensure parity with existing content-type handling expectations.

Changes:

  • Update content-type selection logic to match application/json* and multipart/form-data* entries (including parameterized variants).
  • Add a Zod generation test covering multipart/form-data; charset=utf-8 request bodies and application/json; charset=utf-8 responses.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
packages/zod/src/zod.test.ts Adds a regression test for multipart/form-data request bodies keyed with ; charset=utf-8.
packages/zod/src/index.ts Updates request/response content-type selection to accept parameterized JSON and multipart media types.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/zod/src/index.ts Outdated
Comment thread packages/zod/src/zod.test.ts
Comment thread packages/zod/src/zod.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/zod/src/zod.test.ts (1)

6357-6366: ⚡ Quick win

Add an assertion for charset-qualified response parsing in this scenario.

At Line 6362, this test reuses zodOverride with response: false, so it never validates the response-side charset path even though the fixture includes it. Add a response-enabled variant (or a second assertion block) to verify parser generation for application/json; charset=utf-8 responses.

Also applies to: 6375-6390

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/zod/src/zod.test.ts` around lines 6357 - 6366, The test currently
calls generateZod with zodOverride that has response: false so it never
exercises the response-side charset handling; add a second call (or duplicate
the existing block) invoking generateZod with response enabled (e.g., a copy of
zodOverride but with response: true) for the same operationName/uploadForm and
schema/testOutput, then add an assertion that the generated parser code (the
returned result) contains handling for "application/json; charset=utf-8" (or the
exact charset-qualified content-type string) to validate the response-side
charset-qualified parsing path; reference generateZod, zodOverride,
operationName/uploadForm and the result variable when adding the new assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/zod/src/index.ts`:
- Around line 1339-1345: The media-type matching using startsWith is fragile;
normalize and compare base media types instead: iterate contentEntries (from
resolvedRef.content) and for each contentType split off any parameters at the
first ';', trim and lowercase the base type, then set jsonContent when base ===
'application/json' and formDataContent when base === 'multipart/form-data' so
matches like 'Application/JSON; charset=UTF-8' succeed and unintended prefixes
are not matched.

---

Nitpick comments:
In `@packages/zod/src/zod.test.ts`:
- Around line 6357-6366: The test currently calls generateZod with zodOverride
that has response: false so it never exercises the response-side charset
handling; add a second call (or duplicate the existing block) invoking
generateZod with response enabled (e.g., a copy of zodOverride but with
response: true) for the same operationName/uploadForm and schema/testOutput,
then add an assertion that the generated parser code (the returned result)
contains handling for "application/json; charset=utf-8" (or the exact
charset-qualified content-type string) to validate the response-side
charset-qualified parsing path; reference generateZod, zodOverride,
operationName/uploadForm and the result variable when adding the new assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 527a6045-ce1e-4fce-b8e8-a97eab2c041b

📥 Commits

Reviewing files that changed from the base of the PR and between 729c5b8 and 1dc521e.

📒 Files selected for processing (2)
  • packages/zod/src/index.ts
  • packages/zod/src/zod.test.ts

Comment thread packages/zod/src/index.ts
@melloware melloware added zod Zod schema client related issue openapi OpenAPI related issue labels May 20, 2026

@melloware melloware left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see feedback from AI it made good suggestions

@melloware

Copy link
Copy Markdown
Collaborator

@pierre-isabel-bbc also are you interested in fixing this issue while you are in there: #3285

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/zod/src/index.ts (1)

1424-1427: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make media-type normalization case-insensitive to avoid missed parser generation.

At Line 1427, comparison is still case-sensitive. Valid keys like Application/JSON; charset=UTF-8 won’t match and will skip schema generation.

Suggested fix
 const isMediaType =
   (expectedContentType: string) =>
   ([contentType]: [string, object]): boolean =>
-    contentType.split(';')[0].trim() === expectedContentType;
+    contentType.split(';')[0].trim().toLowerCase() ===
+    expectedContentType.toLowerCase();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/zod/src/index.ts` around lines 1424 - 1427, The media-type
comparison in isMediaType is case-sensitive and can miss matches like
"Application/JSON; charset=UTF-8"; update the check inside isMediaType to
normalize both the incoming contentType and the expectedContentType to the same
case (e.g., toLowerCase()) after trimming and splitting so the comparison is
case-insensitive; locate the isMediaType function and apply normalization to the
contentType base and expectedContentType before comparing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@packages/zod/src/index.ts`:
- Around line 1424-1427: The media-type comparison in isMediaType is
case-sensitive and can miss matches like "Application/JSON; charset=UTF-8";
update the check inside isMediaType to normalize both the incoming contentType
and the expectedContentType to the same case (e.g., toLowerCase()) after
trimming and splitting so the comparison is case-insensitive; locate the
isMediaType function and apply normalization to the contentType base and
expectedContentType before comparing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: df00ccde-49e2-44f8-8630-493fd259ce00

📥 Commits

Reviewing files that changed from the base of the PR and between 1dc521e and 362007e.

📒 Files selected for processing (2)
  • packages/zod/src/index.ts
  • packages/zod/src/zod.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/zod/src/index.ts (1)

1341-1349: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

JSON media-type matcher still misses valid vendor types and case variants.

At Line 1347 and Line 1436, application/vnd.api+json and mixed-case variants can still fail matching, so response/body parsers may be skipped for valid JSON media keys.

Suggested fix
-const isMediaType =
-  (pattern: string) =>
-  ([contentType]: [string, object]): boolean =>
-    new RegExp(pattern).test(contentType.split(';')[0].trim());
+const isMediaType =
+  (pattern: RegExp | string) =>
+  ([contentType]: [string, object]): boolean => {
+    const base = contentType.split(';', 1)[0].trim().toLowerCase();
+    const regex =
+      pattern instanceof RegExp ? pattern : new RegExp(pattern, 'i');
+    return regex.test(base);
+  };
-  const jsonContent = contentEntries.find(
-    isMediaType(
-      // application/json
-      // application/geo+json
-      // application/ld+json
-      // application/manifest+json
-      String.raw`^application\/([\w-]+\+)?json$`,
-    ),
-  );
+  const jsonContent = contentEntries.find(
+    // application/json and application/*+json (RFC token chars)
+    isMediaType(/^application\/(?:json|[a-z0-9!#$&^_.+-]+\+json)$/i),
+  );

Also applies to: 1433-1436

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/zod/src/index.ts` around lines 1341 - 1349, The current regex passed
to isMediaType (used to compute jsonContent and the other identical match later)
misses vendor prefixes and is case-sensitive; update the pattern to allow
vendor-type prefixes (e.g., vnd.<name>+ or any +suffix) and enable
case-insensitive matching when calling isMediaType so media-types like
"application/vnd.api+json" and mixed-case variants match; apply the same change
to both places where the regex is used (the jsonContent computation and the
second identical occurrence).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@packages/zod/src/index.ts`:
- Around line 1341-1349: The current regex passed to isMediaType (used to
compute jsonContent and the other identical match later) misses vendor prefixes
and is case-sensitive; update the pattern to allow vendor-type prefixes (e.g.,
vnd.<name>+ or any +suffix) and enable case-insensitive matching when calling
isMediaType so media-types like "application/vnd.api+json" and mixed-case
variants match; apply the same change to both places where the regex is used
(the jsonContent computation and the second identical occurrence).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0dc208f6-afc2-4b2b-a793-0750e9ca34b1

📥 Commits

Reviewing files that changed from the base of the PR and between 362007e and 4e8b197.

📒 Files selected for processing (2)
  • packages/zod/src/index.ts
  • packages/zod/src/zod.test.ts

@pierre-isabel-bbc

pierre-isabel-bbc commented May 20, 2026

Copy link
Copy Markdown
Author

@pierre-isabel-bbc also are you interested in fixing this issue while you are in there: #3285

@melloware Hey! Done in this commit: 4e8b197

@melloware melloware linked an issue May 20, 2026 that may be closed by this pull request

@melloware melloware left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like now you need to regen snapshots? the Snapshot tests are failing

@melloware
melloware force-pushed the fix/zod-parsers-content-type-w-charset branch from 121275f to a998d35 Compare May 21, 2026 18:20

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/zod/src/zod.test.ts (1)

6409-6489: ⚡ Quick win

Add a dotted vendor JSON media-type regression case.

Please add a case like application/vnd.api+json (optionally with charset) so valid vendor subtypes are covered, not only geo+json/manifest+json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/zod/src/zod.test.ts` around lines 6409 - 6489, The test "json exotic
content type: comprehensive content type handling" is missing a dotted vendor
JSON media-type case; update the requestBody and response content maps to
include 'application/vnd.api+json' (and one variation with a charset like
'application/vnd.api+json; charset=utf-8') alongside 'application/geo+json' and
'application/manifest+json' so vendor subtypes are covered; ensure the test
still calls generateZod (the generateZod invocation and override remain
unchanged) and keep the expected Zod outputs (UploadFormBody /
UploadFormResponse) the same since the schema is identical, only add the new
content-type entries in the spec object.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/zod/src/index.ts`:
- Around line 1341-1348: The media-type regex used when computing jsonContent
(contentEntries.find(... isMediaType(...))) disallows dots in vendor subtype
names (so types like application/vnd.api+json are rejected); update the pattern
passed to isMediaType (the String.raw`^application\/([\w-]+\+)?json$` literal)
to allow dots (and keep existing word, plus and hyphen chars) in the subtype
token (e.g. use a character class that includes '.' such as [\w.-] or [\w.+-])
so vendor subtypes like vnd.api+json are accepted.

---

Nitpick comments:
In `@packages/zod/src/zod.test.ts`:
- Around line 6409-6489: The test "json exotic content type: comprehensive
content type handling" is missing a dotted vendor JSON media-type case; update
the requestBody and response content maps to include 'application/vnd.api+json'
(and one variation with a charset like 'application/vnd.api+json;
charset=utf-8') alongside 'application/geo+json' and 'application/manifest+json'
so vendor subtypes are covered; ensure the test still calls generateZod (the
generateZod invocation and override remain unchanged) and keep the expected Zod
outputs (UploadFormBody / UploadFormResponse) the same since the schema is
identical, only add the new content-type entries in the spec object.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8ae506c3-98e0-4508-a78c-86a568734330

📥 Commits

Reviewing files that changed from the base of the PR and between 121275f and a998d35.

📒 Files selected for processing (2)
  • packages/zod/src/index.ts
  • packages/zod/src/zod.test.ts

Comment thread packages/zod/src/index.ts
Comment on lines +1341 to +1348
const jsonContent = contentEntries.find(
isMediaType(
// application/json
// application/geo+json
// application/ld+json
// application/manifest+json
String.raw`^application\/([\w-]+\+)?json$`,
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

JSON media-type matcher still misses valid vendor subtypes.

The current pattern rejects valid JSON media types like application/vnd.api+json (dot in subtype), so schema generation can still be skipped for legitimate responses/requests.

Suggested fix
   const jsonContent = contentEntries.find(
     isMediaType(
       // application/json
       // application/geo+json
       // application/ld+json
       // application/manifest+json
-      String.raw`^application\/([\w-]+\+)?json$`,
+      // application/vnd.api+json (and other valid vendor subtypes)
+      String.raw`^application\/([^/;]+\+)?json$`,
     ),
   );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/zod/src/index.ts` around lines 1341 - 1348, The media-type regex
used when computing jsonContent (contentEntries.find(... isMediaType(...)))
disallows dots in vendor subtype names (so types like application/vnd.api+json
are rejected); update the pattern passed to isMediaType (the
String.raw`^application\/([\w-]+\+)?json$` literal) to allow dots (and keep
existing word, plus and hyphen chars) in the subtype token (e.g. use a character
class that includes '.' such as [\w.-] or [\w.+-]) so vendor subtypes like
vnd.api+json are accepted.

@melloware
melloware force-pushed the fix/zod-parsers-content-type-w-charset branch from a998d35 to 1fc23d7 Compare May 22, 2026 16:17
@melloware
melloware force-pushed the fix/zod-parsers-content-type-w-charset branch from 1fc23d7 to 431e3fc Compare May 24, 2026 13:55
@melloware melloware closed this May 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

openapi OpenAPI related issue zod Zod schema client related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Zod: application/geo+json response schemas not being generated

3 participants